1   /*
2    * Copyright (C) 2012 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.reflect;
18  
19  import com.google.common.annotations.Beta;
20  
21  import java.lang.reflect.InvocationHandler;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Proxy;
24  import java.util.Arrays;
25  
26  import javax.annotation.Nullable;
27  
28  /**
29   * Abstract implementation of {@link InvocationHandler} that handles {@link Object#equals},
30   * {@link Object#hashCode} and {@link Object#toString}. For example: <pre>
31   * class Unsupported extends AbstractInvocationHandler {
32   *   protected Object handleInvocation(
33   *       Object proxy, Method method, Object[] args) {
34   *     throw new UnsupportedOperationException();
35   *   }
36   * }
37   *
38   * CharSequence unsupported = Reflection.newProxy(CharSequence.class, new Unsupported());
39   * </pre>
40   *
41   * @author Ben Yu
42   * @since 12.0
43   */
44  @Beta
45  public abstract class AbstractInvocationHandler implements InvocationHandler {
46  
47    private static final Object[] NO_ARGS = {};
48  
49    /**
50     * {@inheritDoc}
51     *
52     * <p><ul>
53     * <li>{@code proxy.hashCode()} delegates to {@link AbstractInvocationHandler#hashCode}
54     * <li>{@code proxy.toString()} delegates to {@link AbstractInvocationHandler#toString}
55     * <li>{@code proxy.equals(argument)} returns true if: <ul>
56     *   <li>{@code proxy} and {@code argument} are of the same type
57     *   <li>and {@link AbstractInvocationHandler#equals} returns true for the {@link
58     *       InvocationHandler} of {@code argument}
59     *   </ul>
60     * <li>other method calls are dispatched to {@link #handleInvocation}.
61     * </ul>
62     */
63    @Override public final Object invoke(Object proxy, Method method, @Nullable Object[] args)
64        throws Throwable {
65      if (args == null) {
66        args = NO_ARGS;
67      }
68      if (args.length == 0 && method.getName().equals("hashCode")) {
69        return hashCode();
70      }
71      if (args.length == 1
72          && method.getName().equals("equals")
73          && method.getParameterTypes()[0] == Object.class) {
74        Object arg = args[0];
75        if (arg == null) {
76          return false;
77        }
78        if (proxy == arg) {
79          return true;
80        }
81        return isProxyOfSameInterfaces(arg, proxy.getClass())
82            && equals(Proxy.getInvocationHandler(arg));
83      }
84      if (args.length == 0 && method.getName().equals("toString")) {
85        return toString();
86      }
87      return handleInvocation(proxy, method, args);
88    }
89  
90    /**
91     * {@link #invoke} delegates to this method upon any method invocation on the proxy instance,
92     * except {@link Object#equals}, {@link Object#hashCode} and {@link Object#toString}. The result
93     * will be returned as the proxied method's return value.
94     * 
95     * <p>Unlike {@link #invoke}, {@code args} will never be null. When the method has no parameter,
96     * an empty array is passed in.
97     */
98    protected abstract Object handleInvocation(Object proxy, Method method, Object[] args)
99        throws Throwable;
100 
101   /**
102    * By default delegates to {@link Object#equals} so instances are only equal if they are
103    * identical. {@code proxy.equals(argument)} returns true if: <ul>
104    * <li>{@code proxy} and {@code argument} are of the same type
105    * <li>and this method returns true for the {@link InvocationHandler} of {@code argument}
106    * </ul>
107    * <p>Subclasses can override this method to provide custom equality.
108    */
109   @Override public boolean equals(Object obj) {
110     return super.equals(obj);
111   }
112 
113   /**
114    * By default delegates to {@link Object#hashCode}. The dynamic proxies' {@code hashCode()} will
115    * delegate to this method. Subclasses can override this method to provide custom equality.
116    */
117   @Override public int hashCode() {
118     return super.hashCode();
119   }
120 
121   /**
122    * By default delegates to {@link Object#toString}. The dynamic proxies' {@code toString()} will
123    * delegate to this method. Subclasses can override this method to provide custom string
124    * representation for the proxies.
125    */
126   @Override public String toString() {
127     return super.toString();
128   }
129 
130   private static boolean isProxyOfSameInterfaces(Object arg, Class<?> proxyClass) {
131     return proxyClass.isInstance(arg)
132         // Equal proxy instances should mostly be instance of proxyClass
133         // Under some edge cases (such as the proxy of JDK types serialized and then deserialized)
134         // the proxy type may not be the same.
135         // We first check isProxyClass() so that the common case of comparing with non-proxy objects
136         // is efficient.
137         || (Proxy.isProxyClass(arg.getClass())
138             && Arrays.equals(arg.getClass().getInterfaces(), proxyClass.getInterfaces()));
139   }
140 }